/*
 * Copyright 2020 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.optaplanner.examples.apsplanning.domain.solver;

import org.optaplanner.core.api.domain.variable.VariableListener;
import org.optaplanner.core.api.score.director.ScoreDirector;
import org.optaplanner.examples.apsplanning.domain.ApsPlanningSolution;
import org.optaplanner.examples.apsplanning.domain.BomTask;
import org.optaplanner.examples.apsplanning.domain.BomTaskOrDevice;

import java.util.Objects;


//每次,有BomTask对象的previousBomTaskOrDevice的property发生更改后,这个监听类就会监听到,然后执行相应操作
public class StartTimeUpdatingVariableListener implements VariableListener<ApsPlanningSolution, BomTask> {

    @Override
    public void beforeEntityAdded(ScoreDirector<ApsPlanningSolution> scoreDirector, BomTask bomTask) {
        // Do nothing
    }

    @Override
    public void afterEntityAdded(ScoreDirector<ApsPlanningSolution> scoreDirector, BomTask bomTask) {
        updateStartTime(scoreDirector, bomTask);
    }

    @Override
    public void beforeVariableChanged(ScoreDirector<ApsPlanningSolution> scoreDirector, BomTask bomTask) {
        // Do nothing
    }

    @Override
    public void afterVariableChanged(ScoreDirector<ApsPlanningSolution> scoreDirector, BomTask bomTask) {
        updateStartTime(scoreDirector, bomTask);
    }

    @Override
    public void beforeEntityRemoved(ScoreDirector<ApsPlanningSolution> scoreDirector, BomTask bomTask) {
        // Do nothing
    }

    @Override
    public void afterEntityRemoved(ScoreDirector<ApsPlanningSolution> scoreDirector, BomTask bomTask) {
        // Do nothing
    }

    protected void updateStartTime(ScoreDirector<ApsPlanningSolution> scoreDirector, BomTask sourceTask) {
        //首先取得该Task对象的前一个实体或锚
        BomTaskOrDevice previous = sourceTask.getPreviousBomTaskOrDevice();
        //保存一下当前Task对象
        BomTask shadowTask = sourceTask;
        //取得前面的截止时间
        Integer previousEndTime = (previous == null ? null : previous.getEndTime());
        //通过设备上的任务链和父子节点关系求得bomTask的值
        //开始时间取，前面任务的结束时间，如果后期想要给任务拓展加上前置时间后置时间的话，可以更改一下这个方法
        //刚开始的时候，previousEndTime是null，所以startTime也是null
        Integer startTime = calculateStartTime(shadowTask, previousEndTime);


        //当用来遍历task不是null并且shadowTask的startTime和前面计算的startTime不相等的时候，循环
        //用来遍历的shadowTask一直向后取，一直取到后面为null的时候，也就是previousTaskOrEmployee属性变更的task后面的所有task的startTime全部变更完毕的时候循环停止
        //刚开始的时候，previousEndTime和startTime都是null，所以相等，这种情况就不用更新了，然后后面成链的时候这里才比较有用
        //还有一种就是，两个任务的持续时间一样，这样的话两个任务交换以后后面的任务的时间就不用更新了
        //这个while是进行，这条链上改变节点之后的级联变更，还需要，子节点改变时，其余链上的父节点也会变更，而且，这个变更需要每一次改变都要进行父节点相应的改变，所以需要嵌套在这个while里面
        while (shadowTask != null && !Objects.equals(shadowTask.getStartTime(), startTime)) {

            ////节点发生变更的时候，父节点发生相应变更，直到顶端节点
            ////这个嵌套会内存溢出崩溃。。形成环了？？先不弄这个了
            //if(shadowTask.getParentBom()!=null){
            //    BomTask parentBom = shadowTask.getParentBom();
            //    updateStartTime(scoreDirector, parentBom);
            //}

            //这个方法不知道干了什么
            scoreDirector.beforeVariableChanged(shadowTask, "startTime");
            //设置开始时间为刚计算出来的startTime
            shadowTask.setStartTime(startTime);
            //这个函数不知道干了什么
            scoreDirector.afterVariableChanged(shadowTask, "startTime");

            //前面的截止时间设置为当前任务的截止时间
            previousEndTime = shadowTask.getEndTime();
            //shadowTask继续向后取task
            shadowTask = shadowTask.getNextBomTask();
            //开始时间取，前面的截止时间
            startTime = calculateStartTime(shadowTask, previousEndTime);
        }
    }

    private Integer calculateStartTime(BomTask task, Integer previousEndTime) {
        Integer startTime = 0;
        //如果传进来的task是null，也就是没有到任务链的末端；
        //或者previousEndTime是null，也是不是在任务链的开头
        if (task == null || previousEndTime == null) {
            return 0;
        } else {
            //取到所有的子节点，bomTask节点的startTime取所有子节点的结束时间和当前startTime的最大值
            if (task.getChildBomList() != null) {
                for (BomTask bomTask1 : task.getChildBomList()) {
                    //如果子节点还没有参与排程，startTime为null时，返回的endTime也是null
                    if (bomTask1.getEndTime() == null) {
                        //do nothing==>startTime不变
                    } else {
                        //开始时间，取所有子节点的结束时间的较大值
                        startTime = Math.max(startTime, bomTask1.getEndTime());
                    }
                }
            }
            //2.再取所有子节点结束时间最大值和任务链上前一个任务的结束时间的最大值
            startTime = Math.max(startTime, previousEndTime);
        }
        return startTime;
        //return previousEndTime;
    }
}
